package cn.cas.mango.dbscan;

import cn.cas.mango.util.DateUtil;
import cn.cas.mango.util.ThreadPoolUtils;
import lombok.extern.slf4j.Slf4j;
import org.elasticsearch.common.collect.Tuple;
import org.springframework.stereotype.Component;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
@lombok.Data
@Slf4j
public class GenerateAllNeighbors {
  private List<Data> allData;
  private Map<String, List<Data>> map;
  // 映射分数
  private Map<String, Map<String, Double>> map2Score;
  private Integer range;

  private static Map<Integer, Double> castDay;

  private double e = 0.5;
  // 读取多个时间段的相似度
  private void readSim() {
    if (castDay == null)
      castDay = new ConcurrentHashMap<>();
    String fileName = "day.txt";
    if (e == -1) {
      try (BufferedReader reader = new BufferedReader(new FileReader(fileName))) {
        for (int i = 0; i < 5; i++) {
          String line = reader.readLine();
          if (line == null) {
            log.error("Check your day.txt, invalid line length");
            break;
          }
          try {
            double value = Double.parseDouble(line);
            castDay.put(i, value);
            log.info(fileName + "第 " + (i + 1) + " 行的值为: " + value);
          } catch (NumberFormatException e) {
            log.error(fileName + "第 " + (i + 1) + " 行不是有效的 double 类型。");
          }
        }
      } catch (IOException e) {
        e.printStackTrace();
      }
    } else {
      log.info("cluster mode: e = {}", e);
      for (int i = 0; i < 5; i++) {
        double value = e;
        castDay.put(i, value);
      }
    }
//    }
  }

  private GenerateAllNeighbors(double e) {
    this.e = e;
    map = new ConcurrentHashMap<>();
    map2Score = new ConcurrentHashMap<>();
    readSim();
  }
  public GenerateAllNeighbors(List<Data> allData, double e, Integer range) {
    this(e);
    this.allData = allData;
    this.range = range;
    for (Data allDatum : allData) {
      map.put(allDatum.getId(), new ArrayList<>());
      map2Score.put(allDatum.getId(), new HashMap<>());
    }
  }

  private double transScore(double value) {
    return BigDecimal.valueOf(value)
        .setScale(4, RoundingMode.HALF_UP)
        .doubleValue();
  }
  private int getCastDay(int day) {
    // 0: 0, 1-3: 1, 4-7:2, 8-∞:3
    if (day == 0) {
      return 0;
    } else if (day <= 3) {
      return 1;
    } else if (day <= 7) {
      return 2;
    } else if (day <= 30){
      return 3;
    } else {
      return 4;
    }
  }

public GenerateAllNeighbors() {}
  private void addToMap(Data data, int comparing) {
    Data comparingData = allData.get(comparing);
    double sim = data.calSim(comparingData);
    String id = data.getId();
    int day = (int)DateUtil.getBetweenDay(data.getNewsPublicDate(), comparingData.getNewsPublicDate());
    day = getCastDay(day);
    //delete
//    if (data.getKeywords().getKeywords().contains("微软")) {
//      log.info("data1: {}\n data2: {}, sim: {}", data.getKeywords().getKeywords(), comparingData.getKeywords().getKeywords(), sim);
//    }

      if (sim >= castDay.get(day)) {
      map.get(id).add(comparingData);
      map2Score.get(id).put(comparingData.getId(),transScore(sim));
    }
  }

  /**
   *
   * @param start 分割位置的起点（包含）
   * @param end 分割位置的终点（包含）
   * @param splitNum 要分成多少组
   * @return 包含splitNum组的间隔序列
   */
  private List<Tuple<Integer, Integer>> makeBatch(int start, int end, int splitNum) {
    List<Tuple<Integer, Integer>> ret = new ArrayList<>();
    int range = (end - start + 1) / splitNum;
    for (int i = 0 ; i < splitNum; i++) {
      ret.add(Tuple.tuple(i * range, i * range + range));
    }
    int left = end - start + 1 - range * splitNum;
    if (left != 0) {
      ret.add(Tuple.tuple(end + 1 - left, end + 1));
    }
    return ret;
  }

  public Map<String, List<Data>> getAll() {
    int cpuNum = Runtime.getRuntime().availableProcessors();
    int coreSize = cpuNum * 2;

    List<Tuple<Integer, Integer>> batches = makeBatch(0, allData.size() - 1, coreSize - 1);
    CountDownLatch startGate = new CountDownLatch(batches.size());
    for (Tuple<Integer, Integer> batch: batches) {
      ThreadPoolUtils.execute(() -> getAllItem(batch.v1(), batch.v2(), startGate));
    }
    try {
      startGate.await();
    } catch (InterruptedException ex) {
      throw new RuntimeException(ex);
    }
    return map;
  }

  public Map<String, Map<String, Double>> getMap2Score() {
    return map2Score;
  }

  /**
   *
   * @param start 计算邻居的开始（包含）
   * @param end 集散邻居的结束（不包含）
   * @param startGate 计数变量
   */
  private void getAllItem(int start, int end, CountDownLatch startGate) {

    for (int i = start; i < end; i++) {
      Data data = allData.get(i);
      for (int j = Math.max(i - range, 0); j < (Math.min(i + range, allData.size())); j++) {
        if (j == i) continue;
        addToMap(data, j);
      }
    }
    startGate.countDown();
  }
}
